#include "print.h"
#include "system.h"
#include "FileSystem.h"

int combineInt(char v0, char v1, char v2, char v3){
  return (v0&0x0FF) | ((v1<<8)&0x0FF00) | ((v2<<16)&0x0FF0000) | ((v3<<24)&0x0FF000000);
}

short combineShort(char v0, char v1){
  return (v0&0x0FF) | ((v1<<8)&0x0FF00);
}

StorageDevice* SdVolume::sdCard(void){
  return sdCard_;
}

int SdVolume::printErrorSector(){
  for(int i=0;i<32;i++){
    for(int j=0;j<16;j++){
      printByte(errorSector->cacheBuffer.data[(i<<4)+j]);
    }
    print("\r\n");
  }
  if(initError==4){
    bpb_t* bpb = &errorSector->cacheBuffer.fbs.bpb;
    
    int bytesPerSector = combineShort(bpb->bytesPerSector_0, bpb->bytesPerSector_1);
    int reservedSectorCount = combineShort(bpb->reservedSectorCount_0, bpb->reservedSectorCount_1);

    print("bytesPerSector        =");printInt(bytesPerSector         );print("\r\n");
    print("bpb->fatCount         =");printInt(bpb->fatCount          );print("\r\n");
    print("reservedSectorCount   =");printInt(reservedSectorCount    );print("\r\n");
    print("bpb->sectorsPerCluster=");printInt(bpb->sectorsPerCluster );print("\r\n");
  }
}

int SdVolume::init(StorageDevice* dev, int part) {
  initError = 0;

  {
    for(int i=0;i<CACHE_COUNT;i++){
      cacheEntity[i].cacheDirty = 0;
      cacheEntity[i].cacheBlockNumber = CACHE_BLOCK_INVALID;
      cacheEntity[i].cacheLifeCount = 0;
      cacheEntity[i].cacheMirrorBlock = CACHE_BLOCK_INVALID;
    }
  }

  allocSearchStart_ = 2;
  fatType_ = 0;
  int volumeStartBlock = 0;
  sdCard_ = dev;
  // if part == 0 assume super floppy with FAT boot sector in block zero
  // if part > 0 assume mbr volume with partition table
  cache_entity* cacheBuffer_;
  if (part) {
    if (part > 4)return false;
    if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ, &cacheBuffer_, false, false, 1)){
      initError = 1;
      return false;
    }
    part_t* p = &cacheBuffer_->cacheBuffer.mbr.part[part-1];
    
    //print("cacheBuffer_ =");printInt((int)cacheBuffer_);print("\r\n");

    //print("[");
    //for(int i=0;i<512;i++){
    //  printByte(cacheBuffer_.data[i]);
    //}
    //print("]");

    int totalSectors = combineInt(p->totalSectors_0, p->totalSectors_1, p->totalSectors_2, p->totalSectors_3);
    int firstSector = combineInt(p->firstSector_0, p->firstSector_1, p->firstSector_2, p->firstSector_3);
    //print("p->boot     =");printInt(p->boot     );print("\r\n");
    //print("totalSectors=");printInt(totalSectors);print("\r\n");
    //print("firstSector =");printInt(firstSector );print("\r\n");

    if ((p->boot & 0X7F) !=0  ||
      totalSectors < 100 ||
      firstSector == 0) {
      // not a valid partition
      
      
      initError = 2;
      return false;
    }
    volumeStartBlock = firstSector;
  }

  //print("volumeStartBlock =");printInt(volumeStartBlock );print("\r\n");

  if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ, &cacheBuffer_, false, false, 2)){
    error = 3;
    return false;
  }
  bpb_t* bpb = &cacheBuffer_->cacheBuffer.fbs.bpb;
  
  int bytesPerSector = combineShort(bpb->bytesPerSector_0, bpb->bytesPerSector_1);
  int reservedSectorCount = combineShort(bpb->reservedSectorCount_0, bpb->reservedSectorCount_1);
  if (bytesPerSector != 512 ||
    bpb->fatCount == 0 ||
    reservedSectorCount == 0 ||
    bpb->sectorsPerCluster == 0) {
      errorSector = cacheBuffer_;
      initError = 4;
      return false;
  }
  fatCount_ = bpb->fatCount;
  blocksPerCluster_ = bpb->sectorsPerCluster;

  // determine shift that is same as multiply by blocksPerCluster_
  clusterSizeShift_ = 0;
  while (blocksPerCluster_ != (1 << clusterSizeShift_)) {
    // error if not power of 2
    if (clusterSizeShift_++ > 7) {
      initError = 5;
      return false;
    }
  }
  
  int sectorsPerFat16 = combineShort(bpb->sectorsPerFat16_0, bpb->sectorsPerFat16_1);
  int sectorsPerFat32 = combineInt(bpb->sectorsPerFat32_0, bpb->sectorsPerFat32_1, bpb->sectorsPerFat32_2, bpb->sectorsPerFat32_3);

  blocksPerFat_ = sectorsPerFat16 ?
                    sectorsPerFat16 : sectorsPerFat32;

  fatStartBlock_ = volumeStartBlock + reservedSectorCount;

  // count for FAT16 zero for FAT32
  int rootDirEntryCount = combineShort(bpb->rootDirEntryCount_0, bpb->rootDirEntryCount_1);
  rootDirEntryCount_ = rootDirEntryCount;

  // directory start for FAT16 dataStart for FAT32
  rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_;

  // data start for FAT16 and FAT32
  dataStartBlock_ = rootDirStart_ + (((rootDirEntryCount<<5) + 511)>>9);

  // total blocks for FAT16 or FAT32
  int totalSectors16 = combineShort(bpb->totalSectors16_0, bpb->totalSectors16_1);
  int totalSectors32 = combineInt(bpb->totalSectors32_0, bpb->totalSectors32_1, bpb->totalSectors32_2, bpb->totalSectors32_3);
  int totalBlocks = totalSectors16 ?
                           totalSectors16 : totalSectors32;
  // total data blocks
  clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock);

  // divide by cluster size to get cluster count
  clusterCount_ >>= clusterSizeShift_;

  // FAT type is determined by cluster count
  if (clusterCount_ < 4085) {
    fatType_ = 12;
  } else if (clusterCount_ < 65525) {
    fatType_ = 16;
  } else {
    int fat32RootCluster = combineInt(bpb->fat32RootCluster_0, bpb->fat32RootCluster_1, bpb->fat32RootCluster_2, bpb->fat32RootCluster_3);
    rootDirStart_ = fat32RootCluster;
    fatType_ = 32;
  }
  
  //print("blocksPerFat_");printInt(blocksPerFat_);print("\r\n");//blocksPerFat_+0244   244*(512/2)*512*2
  //print("blocksPerCluster_");printInt(blocksPerCluster_);print("\r\n");//blocksPerCluster_+02

  return true;
}

// find a contiguous group of clusters
int SdVolume::allocContiguous(int count, int* curCluster) {
  // start of group
  int bgnCluster;

  // flag to save place to start next search
  int setStart;

  // set search start cluster
  if (*curCluster) {
    // try to make file contiguous
    bgnCluster = *curCluster + 1;

    // don't save new start location
    setStart = false;
  } else {
    // start at likely place for free cluster
    bgnCluster = allocSearchStart_;

    // save next search start if one cluster
    setStart = 1 == count;
  }
  // end of group
  int endCluster = bgnCluster;

  // last cluster of FAT
  int fatEnd = clusterCount_ + 1;

  // search the FAT for free clusters
  for (int n = 0;; n++, endCluster++) {
    // can't find space checked all clusters
    if (n >= clusterCount_) return false;

    // past end - start from beginning of FAT
    if (endCluster > fatEnd) {
      bgnCluster = endCluster = 2;
    }
    int f;
    if (!fatGet(endCluster, &f)) return false;

    if (f != 0) {
      // cluster in use try next cluster as bgnCluster
      bgnCluster = endCluster + 1;
    } else if ((endCluster - bgnCluster + 1) == count) {
      // done - found space
      break;
    }
  }
  // mark end of chain
  if (!fatPutEOC(endCluster)) return false;

  // link clusters
  while (endCluster > bgnCluster) {
    if (!fatPut(endCluster - 1, endCluster)) return false;
    endCluster--;
  }
  if (*curCluster != 0) {
    // connect chains
    if (!fatPut(*curCluster, bgnCluster)) return false;
  }
  // return first cluster number to caller
  *curCluster = bgnCluster;

  // remember possible next free cluster
  if (setStart) allocSearchStart_ = bgnCluster + 1;

  return true;
}


int SdVolume::blockOfCluster(int position) {
  return (position >> 9) & (blocksPerCluster_ - 1);
}
int SdVolume::clusterStartBlock(int cluster) {
  return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_);
}
int SdVolume::blockNumber(int cluster, int position) {
  return clusterStartBlock(cluster) + blockOfCluster(position);
}

int SdVolume::cacheFlushAll() {
  for(int i=0;i<CACHE_COUNT;i++){
    cacheFlush(&(cacheEntity[i]));
  }
}

void SdVolume::cacheReport() {
  for(int i=0;i<CACHE_COUNT;i++){
    print("cache");printInt(i);print(":\r\n");
    print("cacheBlockNumber");printInt(cacheEntity[i].cacheBlockNumber);print(":\r\n");
    print("cacheDirty      ");printInt(cacheEntity[i].cacheDirty);print(":\r\n");
    print("cacheLifeCount  ");printInt(cacheEntity[i].cacheLifeCount);print(":\r\n");
    print("cacheMirrorBlock");printInt(cacheEntity[i].cacheMirrorBlock);print(":\r\n");
  }
}

int SdVolume::cacheFlush(cache_entity* cache) {
  if (cache->cacheDirty) {
    //print("cacheFlush:");printInt(cache->cacheBlockNumber);print("\r\n");
    if (!sdCard_->writeBlock(sdCard_, cache->cacheBlockNumber, cache->cacheBuffer.data)) {
      error += 100;
      return false;
    }
    // mirror FAT tables
    if (cache->cacheMirrorBlock != CACHE_BLOCK_INVALID) {
      if (!sdCard_->writeBlock(sdCard_, cache->cacheMirrorBlock, cache->cacheBuffer.data)) {
      error += 200;
        return false;
      }
      cache->cacheMirrorBlock = CACHE_BLOCK_INVALID;
    }
    cache->cacheDirty = 0;
  }
  return true;
}

int SdVolume::cacheRawBlock(int blockNumber, int action, cache_entity** cacheBuffer_, int isZero, int isFat, int ID) {
  //print("cacheRawBlock,block:");printInt(blockNumber);print(",ID:");printInt(ID);print(",dirty:");printInt(action);print("\r\n");
  int minLifeVal = 1000000000;
  int minLifeIdx = 0;
  int found = -1;
  for(int i=0;i<CACHE_COUNT;i++){
    if(cacheEntity[i].cacheBlockNumber == blockNumber){
      found = i;
      if(cacheEntity[i].cacheLifeCount<1000000000){//1G
        cacheEntity[i].cacheLifeCount+=CACHE_COUNT;
      }
    }
    if(cacheEntity[i].cacheBlockNumber != CACHE_BLOCK_INVALID && cacheEntity[i].cacheLifeCount>0){
      cacheEntity[i].cacheLifeCount--;
    }
    if(cacheEntity[i].cacheLifeCount < minLifeVal){
      minLifeVal = cacheEntity[i].cacheLifeCount;
      minLifeIdx = i;
    }
    
  }
  if(found!=-1){
    cacheEntity[found].cacheDirty |= action;
    *cacheBuffer_ = &cacheEntity[found];
    //print("found =");printInt(blockNumber);print("\r\n");
    return true;
  }
  
  //print("useCache =");printInt(minLifeIdx);print("\r\n");

  if (!cacheFlush(&cacheEntity[minLifeIdx])){
    error += 10;
    return false;
  }
    

  if(!isZero){

    if (!sdCard_->readBlock(sdCard_, blockNumber, cacheEntity[minLifeIdx].cacheBuffer.data)){
      error += 20;
      return false;
    }
  }
  cacheEntity[minLifeIdx].cacheBlockNumber = blockNumber;
  if(isFat){
    cacheEntity[minLifeIdx].cacheLifeCount+=CACHE_COUNT * 16;
  }else{
    cacheEntity[minLifeIdx].cacheLifeCount+=CACHE_COUNT;
  }
  cacheEntity[minLifeIdx].cacheDirty |= action;
  *cacheBuffer_ = &cacheEntity[minLifeIdx];
  return true;
}

void SdVolume::cacheSetDirty(cache_entity* cacheBuffer_) {
  cacheBuffer_->cacheDirty |= CACHE_FOR_WRITE;
}

// cache a zero block for blockNumber
int SdVolume::cacheZeroBlock(int blockNumber) {
  cache_entity* cacheBuffer_;
  if (!cacheRawBlock(blockNumber, CACHE_FOR_WRITE, &cacheBuffer_, true, false, 3)){
    return false;
  }
  
  // loop take less flash than memset(cacheBuffer_.data, 0, 512);
  for (int i = 0; i < 512; i++) {
    cacheBuffer_->cacheBuffer.data[i] = 0;
  }
  
  return true;
}  


// return the size in bytes of a cluster chain
int SdVolume::chainSize(int cluster, int* size) {
  int s = 0;
  do {
    if (!fatGet(cluster, &cluster)) return false;
    s += 512UL << clusterSizeShift_;
  } while (!isEOC(cluster));
  *size = s;
  return true;
}

// Fetch a FAT entry
int SdVolume::fatGet(int cluster, int* next) {
  if (cluster > (clusterCount_ + 1)) return false;
  int lba = fatStartBlock_;
  lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;

  cache_entity* cacheBuffer_;
  if (!cacheRawBlock(lba, CACHE_FOR_READ, &cacheBuffer_, false, true, 4)) return false;

  if (fatType_ == 16) {
    *next = cacheBuffer_->cacheBuffer.fat16[cluster & 0XFF] & 0xFFFF;
  } else {
    *next = cacheBuffer_->cacheBuffer.fat32[cluster & 0X7F] & FAT32MASK;
  }
  return true;
}

// Store a FAT entry
int SdVolume::fatPut(int cluster, int next) {
  fatPutError=0;
  // error if reserved cluster
  if (cluster < 2) {
    fatPutError = 1;
    return false;
  }

  // error if not in FAT
  if (cluster > (clusterCount_ + 1)) {
    fatPutError = 2;
    return false;
  }

  // calculate block address for entry
  int lba = fatStartBlock_;
  lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;

  cache_entity* cacheBuffer_;
  if (!cacheRawBlock(lba, CACHE_FOR_WRITE, &cacheBuffer_, false, true, 5)) {
    fatPutError = 3;
    return false;
  }
  
  // store entry
  if (fatType_ == 16) {
    cacheBuffer_->cacheBuffer.fat16[cluster & 0XFF] = next;
  } else {
    cacheBuffer_->cacheBuffer.fat32[cluster & 0X7F] = next;
  }
  // mirror second FAT
  if (fatCount_ > 1){
    cacheBuffer_->cacheMirrorBlock = lba + blocksPerFat_;
  }
  return true;
}

int SdVolume::fatPutEOC(int cluster) {
  return fatPut(cluster, 0x0FFFFFFF);
}

// free a cluster chain
int SdVolume::freeChain(int cluster) {
  freeChainError = 0;
  // clear free cluster location
  allocSearchStart_ = 2;

  do {
    int next;
    if (!fatGet(cluster, &next)){
      freeChainError = 1;
      return false;
    }

    // free cluster
    if (!fatPut(cluster, 0)) {
      freeChainError = 2;
      return false;
    }
    
    //print("next:");printInt(next);print("\r\n");

    cluster = next;
  } while (!isEOC(cluster));

  return true;
}


int SdVolume::isEOC(int cluster) {
  return  cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN);
}







/** Directory entry is part of a long name */
int DIR_IS_LONG_NAME(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME;
}
/** Mask for file/subdirectory tests */
/** Directory entry is for a file */
int DIR_IS_FILE(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0;
}
/** Directory entry is for a subdirectory */
int DIR_IS_SUBDIR(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY;
}
int DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_VOLUME_ID) == 0;
}


int SdFile::close(void) {
  //vol_->cacheReport();
  if (!sync())return false;
  //vol_->cacheReport();
  type_ = FAT_FILE_TYPE_CLOSED;
  return true;
}  


/** \return True if this is a SdFile for a directory else false. */
int SdFile::isDir(void)  {return type_ >= FAT_FILE_TYPE_MIN_DIR;}
/** \return True if this is a SdFile for a file else false. */
int SdFile::isFile(void)  {return type_ == FAT_FILE_TYPE_NORMAL;}
/** \return True if this is a SdFile for an open file/directory else false. */
int SdFile::isOpen(void)  {return type_ != FAT_FILE_TYPE_CLOSED;}
/** \return True if this is a SdFile for a subdirectory else false. */
int SdFile::isSubDir(void)  {return type_ == FAT_FILE_TYPE_SUBDIR;}
/** \return True if this is a SdFile for the root directory. */
int SdFile::isRoot(void)  {
  return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32;
}
int SdFile::makeDir(SdFile* parentDir, const char* dirName) {
  dir_t d;
  SdFile* dir = parentDir;
  
  // create a normal file
  if (!open(dir, dirName, O_CREAT | O_EXCL | O_RDWR)) return false;

  // convert SdFile to directory
  flags_ = O_READ;
  type_ = FAT_FILE_TYPE_SUBDIR;

  // allocate and zero first cluster
  if (!addDirCluster())return false;

  // force entry to SD
  if (!sync()) return false;

  // cache entry - should already be in cache due to sync() call
  dir_t* p = cacheDirEntry();
  if (!p) return false;

  // change directory entry  attribute
  p->attributes = DIR_ATT_DIRECTORY;

  // make entry for '.'
  memcpy(&d, p, sizeof(d));
  
  for (int i = 1; i < 11; i++) d.name[i] = ' ';
  d.name[0] = '.';

  // cache block for '.'  and '..'
  int block = vol_->clusterStartBlock(firstCluster_);

  if (!vol_->cacheRawBlock(block, CACHE_FOR_WRITE, &cacheBuffer_, false, false, 6)) return false;

  // copy '.' to block
  memcpy(&(cacheBuffer_->cacheBuffer.dir[0]), &d, sizeof(d));

  // make entry for '..'
  d.name[1] = '.';
  if (dir->isRoot()) {
    //d.firstClusterLow = 0;
    //d.firstClusterHigh = 0;
    d.firstClusterLow_0  = 0;
    d.firstClusterLow_1  = 0;
    d.firstClusterHigh_0 = 0;
    d.firstClusterHigh_1 = 0;
  } else {
    d.firstClusterLow_0  = (dir->firstCluster_) & 0xFF;
    d.firstClusterLow_1  = (dir->firstCluster_>>8) & 0xFF;
    d.firstClusterHigh_0 = (dir->firstCluster_>>16) & 0xFF;
    d.firstClusterHigh_1 = (dir->firstCluster_>>24) & 0xFF;
  }
  // copy '..' to block
  memcpy(&(cacheBuffer_->cacheBuffer.dir[1]), &d, sizeof(d));

  // set position after '..'
  curPosition_ = 2 * sizeof(d);

  // write first block
  return vol_->cacheFlush(cacheBuffer_);
}

int SdFile::open(SdFile* dirFile, const char* fileName, int oflag) {
  fileError = 0;
  
  type_ = FAT_FILE_TYPE_CLOSED;
  char dname[11];
  dir_t* p;

  // error if already open
  if (isOpen()){
    fileError += 1;
    return false;
  }
  if (!make83Name(fileName, dname)){
    fileError += 2;
    return false;
  }
  vol_ = dirFile->vol_;
  dirFile->rewind();

  // bool for empty entry found
  int emptyFound = false;

  // search for file
  while (dirFile->curPosition_ < dirFile->fileSize_) {
    int index = 0XF & (dirFile->curPosition_ >> 5);
    p = dirFile->readDirCache();
    if (p == NULL) {
      fileError += 3;
      return false;
    }
    
    if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
      // remember first empty slot
      if (!emptyFound) {
        emptyFound = true;
        dirIndex_ = index;
        //print("cacheBuffer_->cacheBlockNumber:");printInt(dirFile->cacheBuffer_->cacheBlockNumber);print("\r\n");
        dirBlock_ = dirFile->cacheBuffer_->cacheBlockNumber;
      }
      // done if no entries follow
      if (p->name[0] == DIR_NAME_FREE) break;
    } else {
      //for(int i=0;i<11;i++){
      //  printByte(dname[i]);
      //  printByte(p->name[i]);
      //}
      //print(dname,11);
      //print(p->name, 11);
      int ret = equal(dname, p->name, 11);
      //printInt(ret);
      //print("\r\n");
      if (ret) {
        // don't open existing file if O_CREAT and O_EXCL
        if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) {
          fileError += 4;
          return false;
        }

        // open found file
        if(!openCachedEntry(0XF & index, oflag, dirFile)){
          fileError += 5;
          return false;
        }else{
          return true;
        }
      }
    }
  }
  // only create file if O_CREAT and O_WRITE
  if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) {
    fileError += 6;
    return false;
  }

  // cache found slot or add cluster if end of file
  if (emptyFound) {
    print("emptyFound\r\n");
    p = cacheDirEntry();
    if (!p) {
      fileError += 7;
      return false;
    }
  } else {
    if (dirFile->type_ == FAT_FILE_TYPE_ROOT16) {
      fileError += 8;
      return false;
    }

    // add and zero cluster for dirFile - first cluster is in cache for write
    if (!dirFile->addDirCluster()) {
      fileError += 9;
      return false;
    }

    // use first entry in cluster
    dirIndex_ = 0;
    p = dirFile->cacheBuffer_->cacheBuffer.dir;
  }
  // initialize as empty file
  //memset(p, 0, sizeof(dir_t));
  for(int i=0;i<sizeof(dir_t);i++){
    ((char*)p)[i]=0;
  }
  memcpy(p->name, dname, 11);
  
  // force write of entry to SD
  if (!vol_->cacheFlush(dirFile->cacheBuffer_)) {
    fileError += 9;
    return false;
  }

  // open entry in cache
  if(!openCachedEntry(dirIndex_, oflag, dirFile)){
    fileError += 9;
    return false;
  }else{
    return true;
  }
  return true;
}

int SdFile::openRoot(SdVolume* vol) {
  type_ = FAT_FILE_TYPE_CLOSED;
  // error if file is already open
  if (isOpen()) return false;

  if (vol->fatType_ == 16) {
    type_ = FAT_FILE_TYPE_ROOT16;
    firstCluster_ = 0;
    fileSize_ = 32 * vol->rootDirEntryCount_;
  } else if (vol->fatType_ == 32) {
    type_ = FAT_FILE_TYPE_ROOT32;
    firstCluster_ = vol->rootDirStart_;
    if (!vol->chainSize(firstCluster_, &fileSize_)) return false;
  } else {
    // volume is not initialized or FAT12
    return false;
  }
  vol_ = vol;
  // read only
  flags_ = O_READ;

  // set to start of file
  curCluster_ = 0;
  curPosition_ = 0;

  // root has no directory entry
  dirBlock_ = 0;
  dirIndex_ = 0;
  return true;
}

  
void printDirName(const dir_t& dir, int width) {
  int w = 0;
  for (int i = 0; i < 11; i++) {
    if (dir.name[i] == ' ')continue;
    if (i == 8) {
      putc('.');
      w++;
    }
    putc(dir.name[i]);
    w++;
  }
  if (DIR_IS_SUBDIR(&dir)) {
    putc('/');
    w++;
  }
  while (w < width) {
    putc(' ');
    w++;
  }
}

void SdFile::dirList(int page, int limit) {
  fileError = 0;
  dir_t* p;

  rewind();
  int count1 = 0;
  int count2 = 0;
  while (true) {
    p = readDirCache();
    if(!p){
      //print("[err");printInt(fileError);print("]\r\n");
      break;
    }
    // done if past last used entry
    if (p->name[0] == DIR_NAME_FREE) {
      print("[end]\r\n");
      break;
    }

    // skip deleted entry and entries for . and  ..
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(p)) continue;

    if(count1>page){
      char buff[10];
      scan(buff, 10);
      count1 = 0;
    }
    if(count2>limit){
      break;
    }
    count1++;
    count2++;

    // print any indent spaces
    //      for (int8_t i = 0; i < indent; i++) putc(' ');

    // print file name with possible blank fill
    //printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0);
    printDirName(*p, 14);

    // print modify date/time if requested
//      if (flags & LS_DATE) {
//         printFatDate(p->lastWriteDate);
//         putc(' ');
//         printFatTime(p->lastWriteTime);
//      }
    // print size if requested
    if (!DIR_IS_SUBDIR(p)) {// && (flags & LS_SIZE)
      putc(' ');
      int fileSize = combineInt(p->fileSize_0, p->fileSize_1, p->fileSize_2, p->fileSize_3);
      printInt(fileSize);
    }
    print("\r\n");

    // list subdirectory content if requested
//      if ((flags & LS_R) && DIR_IS_SUBDIR(p)) {
//        uint16_t index = curPosition()/32 - 1;
//        SdFile s;
//        if (s.open(this, index, O_READ)) s.ls(flags, indent + 2);
//        seekSet(32 * (index + 1));
//      }
  }
}

void printDirName(const dir_t& dir, char* buff, int width) {
  int w = 0;
  for (int i = 0; i < 11; i++) {
    if (dir.name[i] == ' ')continue;
    if (i == 8) {
      *buff++ = '.';
      w++;
    }
    *buff++ = dir.name[i];
    w++;
  }
  if (DIR_IS_SUBDIR(&dir)) {
    *buff++ = '/';
    w++;
  }
  while (w < width) {
    *buff++ = ' ';
    w++;
  }
}

void SdFile::dirList(void* param, int(*callback)(void*, char*, int)) {
  fileError = 0;
  dir_t* p;

  rewind();

  while (true) {
    p = readDirCache();
    if(!p){
      //print("[err");printInt(fileError);print("]\r\n");
      break;
    }
    // done if past last used entry
    if (p->name[0] == DIR_NAME_FREE) {
      break;
    }

    // skip deleted entry and entries for . and  ..
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(p)) continue;

    // print any indent spaces
    //      for (int8_t i = 0; i < indent; i++) putc(' ');

    // print file name with possible blank fill
    //printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0);
    
    char buff[14];
    
    printDirName(*p, buff, 14);
    
    int fileSize = 0;
    if (!DIR_IS_SUBDIR(p)) {// && (flags & LS_SIZE)
      fileSize = combineInt(p->fileSize_0, p->fileSize_1, p->fileSize_2, p->fileSize_3);
    }
    
    int ret = callback(param, buff, fileSize);
    
    if(!ret)break;
    // print modify date/time if requested
//      if (flags & LS_DATE) {
//         printFatDate(p->lastWriteDate);
//         putc(' ');
//         printFatTime(p->lastWriteTime);
//      }
    // print size if requested

    // list subdirectory content if requested
//      if ((flags & LS_R) && DIR_IS_SUBDIR(p)) {
//        uint16_t index = curPosition()/32 - 1;
//        SdFile s;
//        if (s.open(this, index, O_READ)) s.ls(flags, indent + 2);
//        seekSet(32 * (index + 1));
//      }
  }
}

/**
 * Read the next byte from a file.
 *
 * \return For success read returns the next byte in the file as an int.
 * If an error occurs or end of file is reached -1 is returned.
 */
int SdFile::read() {
  char b;
  int result = read(&b, 1);
  if(result == 1){
    return ((int)b)&0xFF;
  }else{
    fileError += 5000;
    return -1;
  }
}

int SdFile::read(char* buf, int nbyte) {
  char* dst = buf;

  // error if not open or write only
  if (!isOpen() || !(flags_ & O_READ)){
    fileError += 1000;
    return -1;
  }

  // max bytes left in file
  if (nbyte > (fileSize_ - curPosition_)) nbyte = fileSize_ - curPosition_;

  // amount left to read
  int toRead = nbyte;
  while (toRead > 0) {
    int block;  // raw device block number
    int offset = curPosition_ & 0X1FF;  // offset in block
    if (type_ == FAT_FILE_TYPE_ROOT16) {
      block = vol_->rootDirStart_ + (curPosition_ >> 9);
    } else {
      int blockOfCluster = vol_->blockOfCluster(curPosition_);
      if (offset == 0 && blockOfCluster == 0) {
        // start of new cluster
        if (curPosition_ == 0) {
          // use first cluster in file
          curCluster_ = firstCluster_;
        } else {
          // get next cluster from FAT
          if (!vol_->fatGet(curCluster_, &curCluster_)) {
            fileError += 2000;
            return -1;
          }
        }
      }
      block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
    }
    int n = toRead;
    // amount to be read from current block
    if (n > (512 - offset)) n = 512 - offset;

    // read block to cache and copy data to caller

    if (!vol_->cacheRawBlock(block, CACHE_FOR_READ, &cacheBuffer_, false, false, 7)) {
      fileError += 4000;
      return -1;
    }
    char* src = cacheBuffer_->cacheBuffer.data + offset;
    char* end = src + n;
    while (src != end) *dst++ = *src++;
  
    curPosition_ += n;
    toRead -= n;
  }
  return nbyte;
}

int SdFile::del(SdFile* dirFile, const char* fileName) {
  delError = 0;
  if (!open(dirFile, fileName, O_WRITE)) {
    delError = 1;
    return false;
  }
  if(isSubDir()){
    if(!rmDir()){
      delError = 2;
      return false;
    }
  }
  else{
    if(!remove()){
      delError = 3;
      return false;
    }
  }
  return true;
}

int SdFile::remove(void) {
  removeError = 0;
  // free any clusters - will fail if read-only or directory
  if (!truncate(0)) {
    removeError = 1;
    return false;
  }

  // cache directory entry
  dir_t* d = cacheDirEntry();
  if (!d) {
    removeError = 2;
    return false;
  }

  // mark entry deleted
  d->name[0] = DIR_NAME_DELETED;

  // set this SdFile closed
  type_ = FAT_FILE_TYPE_CLOSED;

  // write entry to SD
  if(!vol_->cacheFlush(cacheBuffer_)){
    removeError = 3;
    return false;
  }
  
  return true;
}
  
/** Set the file's current position to zero. */
void SdFile::rewind(void) {
  curPosition_ = curCluster_ = 0;
}

int SdFile::rmDir(void) {
  // must be open subdirectory
  //if (!isSubDir()) return false;

  rewind();

  // make sure directory is empty
  while (curPosition_ < fileSize_) {
    dir_t* p = readDirCache();
    if (p == NULL) return false;
    // done if past last used entry
    if (p->name[0] == DIR_NAME_FREE) break;
    // skip empty slot or '.' or '..'
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;
    // error not empty
    if (DIR_IS_FILE_OR_SUBDIR(p)) return false;
  }
  // convert empty directory to normal file for remove
  type_ = FAT_FILE_TYPE_NORMAL;
  flags_ |= O_WRITE;
  return remove();
}

/** Set the files position to current position + \a pos. See seekSet(). */
int SdFile::seekCur(int pos) {
  return seekSet(curPosition_ + pos);
}
/**
 *  Set the files current position to end of file.  Useful to position
 *  a file for append. See seekSet().
 */
int SdFile::seekEnd(void) {return seekSet(fileSize_);}
/**
 * Sets a file's position.
 *
 * \param[in] pos The new position in bytes from the beginning of the file.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
int SdFile::seekSet(int pos) {
  // error if file not open or seek past end of file
  if (!isOpen() || pos > fileSize_) return false;

  if (type_ == FAT_FILE_TYPE_ROOT16) {
    curPosition_ = pos;
    return true;
  }
  if (pos == 0) {
    // set position to start of file
    curCluster_ = 0;
    curPosition_ = 0;
    return true;
  }
  // calculate cluster index for cur and new position
  int nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9);
  int nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9);

  if (nNew < nCur || curPosition_ == 0) {
    // must follow chain from first cluster
    curCluster_ = firstCluster_;
  } else {
    // advance from curPosition
    nNew -= nCur;
  }
  while (nNew--) {
    if (!vol_->fatGet(curCluster_, &curCluster_)) return false;
  }
  curPosition_ = pos;
  return true;
}



//  uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
//          uint8_t hour, uint8_t minute, uint8_t second);
/**
 * The sync() call causes all modified data and directory fields
 * to be written to the storage device.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include a call to sync() before a file has been
 * opened or an I/O error.
 */
int SdFile::sync(void) {
  // only allow open files and directories
  if (!isOpen()) return false;

  if (flags_ & F_FILE_DIR_DIRTY) {
    dir_t* d = cacheDirEntry();
    if (!d) return false;

    // do not set filesize for dir files
    if (!isDir()){
      //d->fileSize = fileSize_;
      d->fileSize_0 = (fileSize_) & 0xFF;
      d->fileSize_1 = (fileSize_>>8) & 0xFF;
      d->fileSize_2 = (fileSize_>>16) & 0xFF;
      d->fileSize_3 = (fileSize_>>24) & 0xFF;
    }
    // update first cluster fields
    //d->firstClusterLow = firstCluster_ & 0XFFFF;
    //d->firstClusterHigh = firstCluster_ >> 16;
    d->firstClusterLow_0  = (firstCluster_) & 0xFF;
    d->firstClusterLow_1  = (firstCluster_>>8) & 0xFF;
    d->firstClusterHigh_0 = (firstCluster_>>16) & 0xFF;
    d->firstClusterHigh_1 = (firstCluster_>>24) & 0xFF;

    // set modify time if user supplied a callback date/time function
//      if (dateTime_) {
//        dateTime_(&d->lastWriteDate, &d->lastWriteTime);
//        d->lastAccessDate = d->lastWriteDate;
//      }
    // clear directory dirty
    flags_ &= ~F_FILE_DIR_DIRTY;
  }
  return vol_->cacheFlushAll();//TODO
}  

/**
 * Truncate a file to a specified length.  The current file position
 * will be maintained if it is less than or equal to \a length otherwise
 * it will be set to end of file.
 *
 * \param[in] length The desired length for the file.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include file is read only, file is a directory,
 * \a length is greater than the current file size or an I/O error occurs.
 */
int SdFile::truncate(int length) {
  truncateError = 0;
// error if not a normal file or read-only
  if (!isFile() || !(flags_ & O_WRITE)){
    truncateError = 1;
    return false;
  }

  // error if length is greater than current size
  if (length > fileSize_){
    truncateError = 2;
    return false;
  }

  // fileSize and length are zero - nothing to do
  if (fileSize_ == 0) return true;

  // remember position for seek after truncation
  int newPos = curPosition_ > length ? length : curPosition_;

  // position to last cluster in truncated file
  if (!seekSet(length)) {
    truncateError = 3;
    return false;
  }

  if (length == 0) {
    // free all clusters
    //print("this:");printInt((int)this);print("\r\n");
    //print("firstCluster_:");printInt(firstCluster_);print("\r\n");
    if (!vol_->freeChain(firstCluster_)) {
      truncateError = 4;
      return false;
    }
    firstCluster_ = 0;
  } else {
    int toFree;
    if (!vol_->fatGet(curCluster_, &toFree)){
      truncateError = 5;
      return false;
    }

    if (!vol_->isEOC(toFree)) {
      // free extra clusters
      if (!vol_->freeChain(toFree)){
        truncateError = 6;
        return false;
      }

      // current cluster is end of chain
      if (!vol_->fatPutEOC(curCluster_)){
        truncateError = 7;
        return false;
      }
    }
  }
  fileSize_ = length;

  // need to update directory entry
  flags_ |= F_FILE_DIR_DIRTY;

  if (!sync()) {
    truncateError = 8;
    return false;
  }

  // set file to correct position
  return seekSet(newPos);
}  

/** \return SdVolume that contains this file. */
SdVolume* SdFile::volume(void) {return vol_;}

int SdFile::write(int b) {
  return write(&b, 1);
}
//size_t write(const void* buf, uint16_t nbyte);
int SdFile::write(const void* buf, int nbyte) {
  // convert void* to uint8_t*  -  must be before goto statements
  const char* src = reinterpret_cast<const char*>(buf);

  // number of bytes left to write  -  must be before goto statements
  int nToWrite = nbyte;

  // error if not a normal file or is read-only
  if (!isFile() || !(flags_ & O_WRITE)) goto writeErrorReturn;

  // seek to end of file if append flag
  if ((flags_ & O_APPEND) && curPosition_ != fileSize_) {
    if (!seekEnd()) goto writeErrorReturn;
  }

  while (nToWrite > 0) {
    int blockOfCluster = vol_->blockOfCluster(curPosition_);
    int blockOffset = curPosition_ & 0X1FF;
    if (blockOfCluster == 0 && blockOffset == 0) {
      // start of new cluster
      if (curCluster_ == 0) {
        if (firstCluster_ == 0) {
          // allocate first cluster of file
          if (!addCluster()) goto writeErrorReturn;
        } else {
          curCluster_ = firstCluster_;
        }
      } else {
        int next;
        if (!vol_->fatGet(curCluster_, &next)) return false;
        if (vol_->isEOC(next)) {
          // add cluster if at end of chain
          if (!addCluster()) goto writeErrorReturn;
        } else {
          curCluster_ = next;
        }
      }
    }
    // max space in block
    int n = 512 - blockOffset;

    // lesser of space and amount to write
    if (n > nToWrite) n = nToWrite;

    // block for data write
    int block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
    //if (n == 512) {
    //  // full block - don't need to use cache
    //  // invalidate cache if block is in cache
    //  if (SdVolume::cacheBlockNumber_ == block) {
    //    SdVolume::cacheBlockNumber_ = 0XFFFFFFFF;
    //  }
    //  if (!vol_->writeBlock(block, src)) goto writeErrorReturn;
    //  src += 512;
    //} else 
    {
      //if (blockOffset == 0 && curPosition_ >= fileSize_) {
      //  // start of new block don't need to read into cache
      //  if (!SdVolume::cacheFlush()) goto writeErrorReturn;
      //  SdVolume::cacheBlockNumber_ = block;
      //  SdVolume::cacheSetDirty();
      //} else {
        // rewrite part of block
        //if (!SdVolume::cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) {
        if (!vol_->cacheRawBlock(block, CACHE_FOR_WRITE, &cacheBuffer_, false, false, 8)) {
          goto writeErrorReturn;
        }
      //}
      char* dst = (char*)(((int)cacheBuffer_->cacheBuffer.data) + blockOffset);
      char* end = dst + n;
      while (dst != end) *dst++ = *src++;
    }
    nToWrite -= n;
    curPosition_ += n;
  }
  fileSize_ = curPosition_;
  flags_ |= F_FILE_DIR_DIRTY;

  if (flags_ & O_SYNC) {
    if (!sync()) goto writeErrorReturn;
  }
  return nbyte;

 writeErrorReturn:
  // return for write error
  //writeError = true;
  //setWriteError();
  return 0;
}


// private functions
// add a cluster to a file
int SdFile::addCluster() {
  if (!vol_->allocContiguous(1, &curCluster_)) return false;

  // if first cluster of file link to directory entry
  if (firstCluster_ == 0) {
    firstCluster_ = curCluster_;
    flags_ |= F_FILE_DIR_DIRTY;
  }
  return true;
}

// Add a cluster to a directory file and zero the cluster.
// return with first block of cluster in the cache
int SdFile::addDirCluster(void) {
  if (!addCluster()) return false;

  // zero data in cluster insure first cluster is in cache
  int block = vol_->clusterStartBlock(curCluster_);
  for (int i = vol_->blocksPerCluster_; i != 0; i--) {
    if (!vol_->cacheZeroBlock(block + i - 1)) return false;
  }
  // Increase directory file size by cluster size
  fileSize_ += 512UL << vol_->clusterSizeShift_;
  return true;
}
// cache a file's directory entry
// return pointer to cached entry or null for failure
dir_t* SdFile::cacheDirEntry() {

  if (!vol_->cacheRawBlock(dirBlock_, CACHE_FOR_WRITE, &cacheBuffer_, false, false, 9)) return NULL;
  return cacheBuffer_->cacheBuffer.dir + dirIndex_;
}

//  static void (*dateTime_)(uint16_t* date, uint16_t* time);

//------------------------------------------------------------------------------
// format directory name field from a 8.3 name string
int SdFile::make83Name(const char* str, char* name) {
  char c;
  int n = 7;  // max index for part before dot
  int i = 0;
  // blank fill name and extension
  while (i < 11) name[i++] = ' ';
  i = 0;
  while ((c = *str++) != '\0' && i<11) {
    if (c == '.') {
      if (n == 10) return false;  // only one dot allowed
      n = 10;  // max index for full 8.3 name
      i = 8;   // place for extension
    } else {
      // illegal FAT characters
      int b;
      const char* valid = "|<>^+=?/[];,*\"\\";
      const char *p = valid;
      while ((b = *p++)) if (b == c) return false;
      // check size and only allow ASCII printable characters
      if (i > n || c < 0X21 || c > 0X7E)return false;
      // only upper case allowed in 8.3 names - convert lower to upper
      name[i++] = c < 'a' || c > 'z' ?  c : c + ('A' - 'a');
    }
  }
  // must have a file name, extension is optional
  return name[0] != ' ';
}  

// open a cached directory entry. Assumes vol_ is initializes
int SdFile::openCachedEntry(int dirIndex, int oflag, SdFile* dirFile) {
  // location of entry in cache
  dir_t* p = dirFile->cacheBuffer_->cacheBuffer.dir + dirIndex;

  // write or truncate is an error for a directory or read-only file
  if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) {
    if (oflag & (O_WRITE | O_TRUNC)) {
      fileError += 10;
      return false;
    }
  }
  // remember location of directory entry on SD
  dirIndex_ = dirIndex;
  dirBlock_ = dirFile->cacheBuffer_->cacheBlockNumber;

  
  // copy first cluster number for directory fields
  //firstCluster_ = (uint32_t)p->firstClusterHigh << 16;
  //firstCluster_ |= p->firstClusterLow;
  firstCluster_ = combineInt(p->firstClusterLow_0, p->firstClusterLow_1, p->firstClusterHigh_0, p->firstClusterHigh_1);
  //print("this:");printInt((int)this);print("\r\n");
  //print("firstCluster_:");printInt(firstCluster_);print("\r\n");
  // make sure it is a normal file or subdirectory
  //print("dir->attributes:");printInt(p->attributes);print("\r\n");
  //print("dirIndex:");printInt(dirIndex);print("\r\n");
  if (DIR_IS_FILE(p)) {
    //fileSize_ = p->fileSize;
    fileSize_ = combineInt(p->fileSize_0, p->fileSize_1, p->fileSize_2, p->fileSize_3);
    //print("cacheBuffer_:");printInt((int)dirFile->cacheBuffer_);print("\r\n");
    type_ = FAT_FILE_TYPE_NORMAL;
  } else if (DIR_IS_SUBDIR(p)) {
    if (!vol_->chainSize(firstCluster_, &fileSize_)) {
      fileError += 20;
      return false;
    }
    type_ = FAT_FILE_TYPE_SUBDIR;
  } else {
    fileError += 30;
    return false;
  }
  // save open flags for read/write
  flags_ = oflag & (O_ACCMODE | O_SYNC | O_APPEND);

  // set to start of file
  curCluster_ = 0;
  curPosition_ = 0;

  // truncate file to zero length if requested
  if (oflag & O_TRUNC) {
    if(!truncate(0)){
      fileError += 40;
      return false;
    }else{
      return true;
    }
  }
  return true;
}  

// Read next directory entry into the cache
// Assumes file is correctly positioned
dir_t* SdFile::readDirCache(void) {
  // error if not directory
  if (!isDir()){
    fileError += 100;
    return NULL;
  }

  // index of entry in cache
  int i = (curPosition_ >> 5) & 0XF;

  // use read to locate and cache block
  if (read() < 0) {
    
    fileError += 200;
    return NULL;
  }

  // advance to next entry
  curPosition_ += 31;

  // return pointer to entry
  return (cacheBuffer_->cacheBuffer.dir + i);
}

